package cn.xiaobai.tools;

import org.apache.commons.compress.utils.IOUtils;

import java.io.*;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;
import java.util.jar.*;
import java.util.zip.ZipEntry;

import static cn.xiaobai.tools.Commons.DOT_CLASS;

/**
 * @Author yangdaji
 * @Date 2023/10/26
 * @PackageName cn.xiaobai.tools
 * @ClassName AbstractPackager
 */
public abstract class AbstractPackager {

    private static String MANIFEST = "META-INF/MANIFEST.MF";

    private static String META_INF = "META-INF";

    protected final void write(JarFile sourceJarFile, File destination, File launcherLoaderFile) throws IOException {
        try (JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(destination))) {
            // 构建所需 manifest 内容, 设置自定义引导类和启动类
            buildManifest(sourceJarFile);
            Enumeration<JarEntry> entrys = sourceJarFile.entries();
            while (entrys.hasMoreElements()) {
                JarEntry jarEntry = entrys.nextElement();
                if (jarEntry.isDirectory()) {
                    continue;
                }
                if (isCopy(jarEntry, jarOutputStream)) {
                    try(InputStream entryInputStream = sourceJarFile.getInputStream(jarEntry)) {
                        IOUtils.copy(entryInputStream, jarOutputStream);
                    }
                }

            }
            File manifestOutPath = new File(destination.getParentFile(), MANIFEST);
            // 写入匹配的manifest 值
            writeManifest(manifestOutPath, sourceJarFile.getManifest());
            // 更新 jar 内容
            updateJarFile(jarOutputStream, manifestOutPath);

            // 写入引导依赖包
            writeLauncherLib(jarOutputStream, launcherLoaderFile);

            // todo 目前存在问题:
            //      如何将当前所依赖的 jar 一起写入

        }
    }

    private boolean isCopy(JarEntry jarEntry, JarOutputStream jarOutputStream) throws IOException {
        String entryName = jarEntry.getName();
        if (!entryName.contains(META_INF)) {
            // 定义 zip entry , 设置新的目录,并将编译的文件拷贝到这个位置
            ZipEntry zipEntry = new JarEntry(getClassOutPath() + entryName);
            jarOutputStream.putNextEntry(zipEntry);
        } else if (!entryName.equals(MANIFEST)) {
            jarOutputStream.putNextEntry(jarEntry);
        } else {
            return false;
        }
        return true;
    }

    /**
     * 写入manifest
     *
     * @param manifestOutPath
     * @param manifest
     * @throws IOException
     */
    protected synchronized final void writeManifest(File manifestOutPath, Manifest manifest) throws IOException {
        if (!manifestOutPath.exists()) {
            manifestOutPath.getParentFile().mkdirs();
        }
        try (FileOutputStream newFileOut = new FileOutputStream(manifestOutPath)) {
            Map<Object, Object> manifestData = manifest.getMainAttributes();
            StringBuilder stringBuilder = new StringBuilder();
            for (Map.Entry<Object, Object> entry : manifestData.entrySet()) {
                stringBuilder.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
            }
            newFileOut.write(stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
        }
    }


    /**
     * 更新jar 文件内容
     *
     * @param jarOutputStream
     * @param manifestOutPath
     * @throws IOException
     */
    protected final void updateJarFile(JarOutputStream jarOutputStream, File manifestOutPath) throws IOException {
        // 写入更新文件
        JarEntry jarEntry = new JarEntry(MANIFEST);
        jarOutputStream.putNextEntry(jarEntry);
        try (InputStream entryInputStream = new FileInputStream(manifestOutPath)) {
            IOUtils.copy(entryInputStream, jarOutputStream);
        } finally {
            manifestOutPath.delete();
            File parentFile = manifestOutPath.getParentFile();
            if (parentFile != null && Array.getLength(parentFile.listFiles()) == 0) {
                parentFile.delete();
            }
        }
    }


    /**
     * 写入引导依赖包
     */
    protected synchronized final void writeLauncherLib(JarOutputStream jarOutputStream, File launcherLoaderFile) throws IOException {
        try (JarFile jarFile = new JarFile(launcherLoaderFile)) {
            // 读取引导jar 写入原jar 中
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String name = jarEntry.getName();
                if (getLoaderPath().equals(name)) {
                    InputStream inputStream = jarFile.getInputStream(jarEntry);
                    // 在创建jar 的输入流
                    JarInputStream jarInputStream = new JarInputStream(inputStream);
                    // 获取这个jar 流中的信息
                    ZipEntry ze = null;
                    while ((ze = jarInputStream.getNextEntry()) != null) {
                        String innName = ze.getName();
                        if (innName.endsWith(DOT_CLASS)) {
                            jarOutputStream.putNextEntry(ze);
                            // 复制原文件到新文件中
                            IOUtils.copy(jarInputStream, jarOutputStream);
                        }
                    }

                }
            }
        }
    }


    protected abstract void buildManifest(JarFile jarFile) throws IOException;

    /**
     * 获取引导包所在位置
     *
     * @return
     */
    protected abstract String getLoaderPath();

    /**
     * 获取class 文件输出位置
     *
     * @return
     */
    protected abstract String getClassOutPath();

    /**
     * 获取class 依赖输出位置
     *
     * @return
     */
    protected abstract String getClassLibPath();

    /**
     * 获取 url 类加载
     *
     * @return
     * @throws IOException
     */
    protected abstract ClassLoader getURLClassLoad() throws IOException;
}
